//
//  LinphoneUtil.swift
//  linphone_flutter
//
//  Created by alexcai on 2022/9/1.
//

import Foundation
import Flutter
import linphonesw


class LinphoneUtil:NSObject{
    static var single: LinphoneUtil!
    var mCore: Core!
    var mRegistrationDelegate : LinphoneDelegate!
    var loggedIn: Bool = false
    var transportType : String = "TCP"
    
    private override init(){
    }
    
    @objc
    static public var Instance: LinphoneUtil
    {
        if(single == nil){
            single = LinphoneUtil()
        }
        return single
    }
    
    @objc
    func start(registrar: FlutterPluginRegistrar){
        LoggingService.Instance.logLevel = LogLevel.Message
        try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil)
        try? mCore.start()
        mRegistrationDelegate = LinphoneDelegate.init(binaryMessenger: registrar.messenger());
        mCore.addDelegate(delegate: mRegistrationDelegate)
    }
    
    @objc
    func getVersion() -> String{
        return Core.getVersion
    }
    
    @objc
    func login(call: FlutterMethodCall, result: FlutterResult){
        let argsMap = call.arguments as! NSDictionary
        let username  = argsMap["username"] as! String
        let passwd = argsMap["password"] as! String
        let domain  = argsMap["domain"] as! String
        do {
            var transport : TransportType
            if (transportType == "TLS") { transport = TransportType.Tls }
            else if (transportType == "TCP") { transport = TransportType.Tcp }
            else  { transport = TransportType.Udp }
            
            let authInfo = try Factory.Instance.createAuthInfo(username: username, userid: "", passwd: passwd, ha1: "", realm: "", domain: domain)
            let accountParams = try mCore.createAccountParams()
            let identity = try Factory.Instance.createAddress(addr: String("sip:" + username + "@" + domain))
            try! accountParams.setIdentityaddress(newValue: identity)
            let address = try Factory.Instance.createAddress(addr: String("sip:" + domain))
            try address.setTransport(newValue: transport)
            try accountParams.setServeraddress(newValue: address)
            accountParams.registerEnabled = true
            let mAccount = try mCore.createAccount(params: accountParams)
            mCore.addAuthInfo(info: authInfo)
            try mCore.addAccount(account: mAccount)
            mCore.defaultAccount = mAccount
            
        } catch { NSLog(error.localizedDescription) }
    }
    
    @objc
    func unregister()
    {
        if let account = mCore.defaultAccount {
            let params = account.params
            let clonedParams = params?.clone()
            clonedParams?.registerEnabled = false
            account.params = clonedParams
        }
    }
    
    @objc
    func delete() {
        if let account = mCore.defaultAccount {
            mCore.removeAccount(account: account)
            mCore.clearAccounts()
            mCore.clearAllAuthInfo()
        }
    }
    
    @objc
    func outgoingCall(call:FlutterMethodCall){
        let argsMap = call.arguments as! NSDictionary
        let remoteSipUri = argsMap["remoteSipUri"] as! String
        do {
            // As for everything we need to get the SIP URI of the remote and convert it to an Address
            let remoteAddress = try Factory.Instance.createAddress(addr: remoteSipUri)
            
            // We also need a CallParams object
            // Create call params expects a Call object for incoming calls, but for outgoing we must use null safely
            let params = try mCore.createCallParams(call: nil)
            
            // We can now configure it
            // Here we ask for no encryption but we could ask for ZRTP/SRTP/DTLS
            params.mediaEncryption = MediaEncryption.None
            // If we wanted to start the call with video directly
            //params.videoEnabled = true
            
            // Finally we start the call
            let _ = mCore.inviteAddressWithParams(addr: remoteAddress, params: params)
            // Call process can be followed in onCallStateChanged callback from core listener
        } catch { NSLog(error.localizedDescription) }
        
    }
    
    
    @objc
    func terminateCall() {
        do {
            // Terminates the call, whether it is ringing or running
            try mCore.currentCall?.terminate()
        } catch { NSLog(error.localizedDescription) }
    }
    
    @objc
    func acceptCall() {
        // IMPORTANT : Make sure you allowed the use of the microphone (see key "Privacy - Microphone usage description" in Info.plist) !
        do {
            // if we wanted, we could create a CallParams object
            // and answer using this object to make changes to the call configuration
            // (see OutgoingCall tutorial)
            try mCore.currentCall?.accept()
        } catch { NSLog(error.localizedDescription) }
    }
    
    @objc
    func hangUp() {
        let currentCall = getCurrentCall()
        if (currentCall == nil) {
            return
        }
        do {
            try currentCall?.terminate()
        } catch { NSLog(error.localizedDescription) }
    }

    @objc
    func transferTo(call:FlutterMethodCall){
        let argsMap = call.arguments as! NSDictionary
        let remoteSipUri = argsMap["remoteSipUri"] as! String
        do {
        let remoteAddress = try Factory.Instance.createAddress(addr:remoteSipUri)
        if (remoteAddress == nil){
            return
        }

        let params = try mCore.createCallParams(call:nil)
        if (params == nil){
            return
        }

        params.mediaEncryption = MediaEncryption.None
        try mCore.currentCall?.transferTo(referTo: remoteAddress)
        } catch { NSLog(error.localizedDescription) }
    }
    
    func getCurrentCall() ->  Call?{
        if (mCore?.callsNb == 0) {
                return nil
            }
        let phoneCall = mCore?.currentCall != nil ?
        mCore?.currentCall : mCore?.calls[0];
        if (phoneCall == nil) {
                return nil
        }
        return phoneCall
    }

    @objc
    func pauseOrResume() {
        do {
            if (mCore.callsNb == 0) { return }
            let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
            
            if let call = coreCall {
                if (call.state != Call.State.Paused && call.state != Call.State.Pausing) {
                    // If our call isn't paused, let's pause it
                    try call.pause()
                } else if (call.state != Call.State.Resuming) {
                    // Otherwise let's resume it
                    try call.resume()
                }
            }
        } catch { NSLog(error.localizedDescription) }
    }
    
    @objc
    func muteMicrophone(result: FlutterResult) {
        // The following toggles the microphone, disabling completely / enabling the sound capture
        // from the device microphone
        mCore.micEnabled = !mCore.micEnabled
        result(mCore.micEnabled)
    }
    
    @objc
    func toggleSpeaker(result: FlutterResult) {
        // Get the currently used audio device
        let currentAudioDevice = mCore.currentCall?.outputAudioDevice
        let speakerEnabled = currentAudioDevice?.type == AudioDeviceType.Speaker
        
        //let test = currentAudioDevice?.deviceName
        // We can get a list of all available audio devices using
        // Note that on tablets for example, there may be no Earpiece device
        for audioDevice in mCore.audioDevices {
            
            // For IOS, the Speaker is an exception, Linphone cannot differentiate Input and Output.
            // This means that the default output device, the earpiece, is paired with the default phone microphone.
            // Setting the output audio device to the microphone will redirect the sound to the earpiece.
            if (speakerEnabled && audioDevice.type == AudioDeviceType.Microphone) {
                mCore.currentCall?.outputAudioDevice = audioDevice
                result(false)
                return
            } else if (!speakerEnabled && audioDevice.type == AudioDeviceType.Speaker) {
                mCore.currentCall?.outputAudioDevice = audioDevice
                result(true)
                return
            }
            /* If we wanted to route the audio to a bluetooth headset
            else if (audioDevice.type == AudioDevice.Type.Bluetooth) {
            core.currentCall?.outputAudioDevice = audioDevice
            }*/
        }
    }
    
    
    
    
    @objc
    func remoteAddressUri(result:FlutterResult) {
        let uri = mCore.currentCall?.remoteAddress?.asStringUriOnly()
        result(uri)
    }
}
